#pragma once

#include <stx/core/random.hpp>

/*
Character Escapes in Regular Expressions
----------------------------------------

{ } [ ] ( ) ^ $ \ | / ? + . *

Hwo to handle nested groups?

If the groupings in a regex are nested, $1 gets the group with the leftmost
opening parenthesis, $2 the next opening parenthesis, etc.

$& gets the current group self in replacement mode.

The replacement_pattern_str is a string that can contain the following
special sequences(in addition to ordinary characters).

$&: Refers to the entire matched string.

$n : Refers to the nth group within the matched string.

For example, "$1" refers to the first group of characters tagged by the
regex object; "$2" refers to the second group of tagged characters (if there
is one), and so on.

How to refer to the nth capture where n > 9?
--------------------------------------------
According to Table 22 in Section 15.5.4.11 of ECMA-262 specification:

Let m be the number of left capturing parentheses in searchValue (using 
NcapturingParens as specified in 15.10.2.1).

$n
--
The nth capture, where n is a single digit in the range 1 to 9 and $n is not
followed by a decimal digit. If n ≤ m and the nth capture is undefined, use the
empty String instead. If n > m, the result is implementation-defined.

$nn
---
The nnth capture, where nn is a two-digit decimal number in the range 01 to 99.
If nn ≤ m and the nnth capture is undefined, use the empty String instead. If 
nn > m, the result is implementation-defined.

See also: https://stackoverflow.com/q/29809811/508343

The example that follows should clarify.

$$: A literal dollar sign($).

"(?<=\bre)\w+\b" || "reading a book"
=> "ading"

"\b\w+(?=ing\b)" || "I'm singing while you're dancing"
=> "sing", "danc"

Negative mode:

"(?<![A-Z])[0-9]+$" || "abZW863ab88"
=> "63ab88"

"ab(?![A-Z])" || "abZW863ab88"
=> only the second "ab" matches

How to look ahead / behind in C++ regex?

Use groups! That is, keeping some groups and discard others.

正则表达式方括号内部的转义规则
-----------------------

[\^\[\-\]\\]

会改变字符组含义的才需要转义：

1、反斜线必须转义；
2、方括号必须转义；
3、「^」在首和「-」在中必须转义；

所以以下常见的字符是不需要转义的：

[aeiou]
[$.*+?{}()|]
[abc^123-]

Notes:

regexSearch(Lazy) returns matched results only, while split(Lazy) returns
non-matched ones. They are complementary.
*/

inline namespace stx
{
[[nodiscard]] inline auto makeRegex(IsStdCharStringLike auto const& s)
{
    constexpr auto is_ansi_char =
        IsIdentical<StringCharType<decltype(s)>, char>;

    try
    {
        if constexpr (is_ansi_char)
        {
            if constexpr (IsStdZstring<decltype(s)>)
            {
                return std::regex(&s[0], std::regex::ECMAScript);
            }
            else
            {
                auto const sv = tosv(s);
                return std::regex(sv.data(), sv.size(), std::regex::ECMAScript);
            }
        }
        else
        {
            if constexpr (IsStdZstring<decltype(s)>)
            {
                return std::wregex(&s[0], std::regex::ECMAScript);
            }
            else
            {
                auto const sv = tosv(s);
                return std::wregex(
                    sv.data(), sv.size(), std::regex::ECMAScript);
            }
        }
    }
    catch (std::exception const& e)
    {
        if constexpr (is_ansi_char)
        {
            RUNTIME_ERROR("Invalid regex: "sv, s, _I_, e.what());
        }
        else
        {
            RUNTIME_ERROR("Invalid wregex: "sv, e.what());
        }
    }
    catch (...)
    {
        if constexpr (is_ansi_char)
        {
            RUNTIME_ERROR("Invalid regex: "sv, s, _I_, "Unknown error."sv);
        }
        else
        {
            RUNTIME_ERROR("Invalid wregex: Unknown error."sv);
        }
    }

    std::abort();
}

template<BasicStringLiteral SL_RGX_PATTERN_>
requires IsStdChar<decltype(*SL_RGX_PATTERN_.data())>
[[nodiscard]] inline auto const& rgx()
{
    static auto const ls = makeRegex(SL_RGX_PATTERN_.tosv());
    return ls;
}

template<IsStdCharStringLike String_, IsStdRegex Regex_>
requires EXPR(IsIdentical<StringCharType<String_>, typename Regex_::value_type>)
[[nodiscard]] inline bool isMatch(String_ const& str, Regex_ const& re)
{
    if constexpr (IsStdZstring<String_>)
    {
        return std::regex_match(&str[0], re);
    }
    else
    {
        auto const sv = tosv(str);
        return std::regex_match(sv.begin(), sv.end(), re);
    }
}

template<IsRawStdChar Char_>
class RegexMatchResults final
{
    std::match_results<Char_ const*> mr_;
    Char_ const*                     sz_src_ = {};

public:
    constexpr RegexMatchResults(std::match_results<Char_ const*>&& mr,
                                Char_ const*                       sz_src)
        : mr_(std::move(mr))
        , sz_src_(sz_src)
    {
        ASSERT(this->sz_src_);
    }

    [[nodiscard]] bool empty() const noexcept
    {
        return !this->mr_.ready() || this->mr_.empty();
    }

    [[nodiscard]] std::size_t size() const
    {
        return this->mr_.size();
    }

    [[nodiscard]] std::basic_string_view<Char_>
    operator[](std::size_t const idx) const
    {
        if (idx >= this->mr_.size())
        {
            RUNTIME_ERROR("Invalid idx: "sv, idx, _I_, this->mr_.size());
        }

        return std::basic_string_view<Char_>(
            &this->sz_src_[Number(this->mr_.position(idx))],
            Number(this->mr_.length(idx)));
    }
};

template<IsStdCharStringLike String_, IsStdRegex Regex_>
requires EXPR(IsIdentical<StringCharType<String_>, typename Regex_::value_type>)
[[nodiscard]] inline auto getMatchResults(String_ const& str, Regex_ const& re)
{
    using Char_ = StringCharType<String_>;
    auto mr     = std::match_results<Char_ const*>{};
    auto sz_src = Ptr<Char_ const>{};

    if constexpr (IsStdZstring<String_>)
    {
        std::regex_match(&str[0], mr, re);
        sz_src = &str[0];
    }
    else
    {
        auto const sv = tosv(str);
        std::regex_match(sv.begin(), sv.end(), mr, re);
        sz_src = &sv[0];
    }

    return RegexMatchResults<Char_>(std::move(mr), sz_src);
}

template<IsStdCharStringLike String_,
         IsStdRegex          Regex_,
         IsStdZstring        ReplacementString_>
requires requires
{
    requires IsConstructibleFromExStdStringView<String_>;
    requires IsConstructibleFromExStdStringView<ReplacementString_>;
    requires AreSimilarStrings<String_, ReplacementString_>;
    requires EXPR(
        IsIdentical<StringCharType<String_>, typename Regex_::value_type>);
}
[[nodiscard]] inline auto regexReplace(String_ const&            s_src,
                                       Regex_ const&             re,
                                       ReplacementString_ const& s_replacement)
{
    if constexpr (IsStdZstring<String_>)
    {
        return std::regex_replace(&s_src[0], re, &s_replacement[0]);
    }
    else
    {
        using StdString_ = std::basic_string<StringCharType<String_>>;

        auto       out = StdString_{};
        auto const sv  = tosv(s_src);

        out.reserve(sv.size() + C_PAGE_SIZE);
        std::regex_replace(std::back_inserter(out),
                           sv.begin(),
                           sv.end(),
                           re,
                           &s_replacement[0]);
        out.shrink_to_fit();
        return out;
    }
}

// `sub_match_idx` is the index of the submatch that should be returned. 0
// represents the entire match, and -1 represents the parts that are not
// matched (e.g, the stuff between matches).
template<std::bidirectional_iterator BidirIterator_, IsStdRegex Regex_>
requires requires
{
    requires IsIdentical <
        typename std::iterator_traits<NoCvr<BidirIterator_>>::value_type,
    typename Regex_::value_type > ;
}
[[nodiscard]] inline auto regexSearch(BidirIterator_ const first,
                                      BidirIterator_ const last,
                                      Regex_ const&        re,
                                      int const            sub_match_idx)
{
    using StringView_ = std::basic_string_view<typename Regex_::value_type>;
    using RegexTokenIterator_ = std::regex_token_iterator<BidirIterator_>;

    return ranges::subrange(
               RegexTokenIterator_(first, last, re, sub_match_idx),
               RegexTokenIterator_{}) |
           views::transform(
               [](auto const& sub_match)
               {
                   if (sub_match.matched)
                   {
                       return StringView_(
                           &*sub_match.first,
                           Number(sub_match.second - sub_match.first));
                   }

                   return StringView_{};
               });
}

template<IsStdCharStringLike String_, IsStdRegex Regex_>
requires EXPR(IsIdentical<StringCharType<String_>, typename Regex_::value_type>)
[[nodiscard]] inline auto
regexSearch(String_ const& str, Regex_ const& re, int const sub_match_idx)
{
    auto const sv = tosv(str);
    return regexSearch(sv.begin(), sv.end(), re, sub_match_idx);
}

template<IsStdCharStringLike String_, IsStdRegex Regex_>
requires EXPR(IsIdentical<StringCharType<String_>, typename Regex_::value_type>)
[[nodiscard]] inline auto
regexSearchFirst(String_ const& str, Regex_ const& re, int const sub_match_idx)
{
    using StringView_ = std::basic_string_view<StringCharType<String_>>;

    auto rng = regexSearch(str, re, sub_match_idx);
    if (std::empty(rng))
    {
        return StringView_{};
    }

    return *std::begin(rng);
}

// split("##abc#xyz#"sv, rgx<"#">())
// or
// split("##abc#xyz"sv, rgx<"#">())

// => [""s, ""s, "abc"s, "xyz"s]
template<IsStdCharStringLike String_, IsStdRegex Regex_>
requires EXPR(IsIdentical<StringCharType<String_>, typename Regex_::value_type>)
[[nodiscard]] auto split(String_ const& str, Regex_ const& re)
{
    return regexSearch(str, re, -1);
}

[[nodiscard]] bool isNaturalNumber(std::string_view const sv);
[[nodiscard]] bool isIp4(std::string_view const sv);
[[nodiscard]] bool isIp6(std::string_view const sv);

// IP:PORT format: "8.8.8.8:12345"sv or "[1fff:0:a88:85a3::ac1f]:8080"sv
[[nodiscard]] bool isIpPort(std::string_view const sv);
} // namespace stx
